package com.leetroll.app.controller;

import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.leetroll.api.service.UserService;
import com.leetroll.api.vo.LoginVO;
import com.leetroll.app.annotation.OperationLogger.OperationLogger;
import com.leetroll.app.annotation.log.BussinessLog;
import com.leetroll.app.config.WePayConfig;
import com.leetroll.app.constant.SysConstant;
import com.leetroll.app.dto.Token;
import com.leetroll.app.service.LoginUserService;
import com.leetroll.app.utils.AppJwtTokenUtil;
import com.leetroll.common.bean.ApiResponse;
import com.leetroll.common.config.jwt.Audience;
import com.leetroll.common.constant.CharConstant;
import com.leetroll.common.constant.MessageConstant;
import com.leetroll.common.constant.RedisConstant;
import com.leetroll.common.constant.SQLConstant;
import com.leetroll.common.entity.User;
import com.leetroll.common.enums.EBehavior;
import com.leetroll.common.enums.EModule;
import com.leetroll.common.utils.*;
import com.leetroll.common.validator.group.Insert;
import com.yungouos.pay.entity.WxOauthInfo;
import com.yungouos.pay.entity.WxUserInfo;
import com.yungouos.pay.wxapi.WxApi;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import io.swagger.annotations.ApiParam;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cloud.context.config.annotation.RefreshScope;
import org.springframework.validation.BindingResult;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;

import javax.servlet.http.HttpServletRequest;
import java.util.concurrent.TimeUnit;

import static com.leetroll.common.enums.ECode.TOKEN_LOSE_EFFECT;

@RestController
@RefreshScope
@Api(value = "登录接口", tags = {"登录接口"})
@Slf4j
public class LoginController {

    private final UserService userService;
    private final LoginUserService loginUserService;
    private final RedisUtil redisUtil;
    private final Audience audience;
    private final AppJwtTokenUtil jwtTokenUtil;

    @Value(value = "${isRememberMeExpiresSecond}")
    private int isRememberMeExpiresSecond;

    //token过期的时间 默认两小时
    @Value(value = "${audience.expiresSecond}")
    private Long expiresSecond;

    @Autowired
    public LoginController(UserService userService, LoginUserService loginUserService, RedisUtil redisUtil,
                           Audience audience, AppJwtTokenUtil jwtTokenUtil) {
        this.userService = userService;
        this.loginUserService = loginUserService;
        this.redisUtil = redisUtil;
        this.audience = audience;
        this.jwtTokenUtil = jwtTokenUtil;
    }

    @OperationLogger(modul = EModule.LOGIN_REGIST,type = "LOGIN-WX",desc = "微信扫码登录")
    @ApiOperation(value = "微信扫码登录", notes = "微信扫码登录")
    @GetMapping("/welogin")
    public ApiResponse<String> weLogin() {
//        String loginCallbackUri = "http://localhost:8808/login_result";
        String loginCallbackUri = "http://www.leetroll.com/login_result";
        String oauthUrl = WxApi.getWxOauthUrl(WePayConfig.mchId, loginCallbackUri, "open-url", null, WePayConfig.KEY);
        return ApiResponse.OK(oauthUrl);
    }

    @ApiOperation(value = "检查微信授权信息", notes = "检查微信授权信息")
    @GetMapping("/check-auth")
    public ApiResponse<Token> checkAuth(HttpServletRequest request, @RequestParam String code) {
        WxOauthInfo oauthInfo = WxApi.getWxOauthInfo(WePayConfig.mchId, code, WePayConfig.KEY);
        //登录失败
        if (oauthInfo == null || StringUtils.isEmpty(code)) return ApiResponse.OK(null);

        //ip信息
        String ip = IpUtils.getIpAddr(request);

        //登录成功, 创建新用户
        WxUserInfo userInfo = oauthInfo.getWxUserInfo();
        User user = userService.getByOpenid(userInfo.getOpenid());
        if (user == null) {
            user = userService.addWeChatUser(userInfo.getOpenid(), userInfo.getNickname(), userInfo.getHeadimgurl(), userInfo.getSex());
        } else {
            userService.updateWeChatUser(user, userInfo.getNickname(), userInfo.getHeadimgurl(), userInfo.getSex());
        }

        //生成Token信息
        Token token = jwtTokenUtil.createToken(user.getName(), user.getId());
        updateAddress(ip, user);

        // 添加在线用户到Redis中【设置过期时间】
        loginUserService.addOnlineUser(user, ip, token.getAccessToken(), audience.getExpiresSecond());

        //将从数据库查询的数据缓存到redis中
        redisUtil.setEx(RedisConstant.USER_TOKEN + CharConstant.SYMBOL_COLON + token.getAccessToken(), JsonUtils.objectToJson(user), expiresSecond, TimeUnit.SECONDS);
        log.info("登录成功, accessToken: {}", token.getAccessToken());
        return ApiResponse.OK(token);
    }

    @OperationLogger(modul = EModule.LOGIN_REGIST,type = "LOGIN",desc = "用户登录")
    @ApiOperation(value = "用户登录", notes = "用户登录")
    @PostMapping("/login")
    public ApiResponse<Token> login(HttpServletRequest request,
                                    @ApiParam(name = "phone", value = "手机号") @RequestParam(name = "phone", required = false) String phone,
                                    @ApiParam(name = "password", value = "密码") @RequestParam(name = "password", required = false) String password,
                                    @ApiParam(name = "isRememberMe", value = "是否记住账号密码") @RequestParam(name = "isRememberMe", required = false, defaultValue = "false") Boolean isRememberMe) {

        if (StringUtils.isEmpty(phone) || StringUtils.isEmpty(password)) {
            return ApiResponse.FAIL("账号或密码不能为空");
        }
        String ip = IpUtils.getIpAddr(request);
        //LOGIN_LIMIT:ip地址
        String limitCount = redisUtil.get(RedisConstant.LOGIN_LIMIT + RedisConstant.SEGMENTATION + ip);

        if (StringUtils.isNotEmpty(limitCount)) {
            int tempLimitCount = Integer.parseInt(limitCount);
            //目前最多输入5次
            if (tempLimitCount >= CharConstant.NUM_FIVE) {
                return ApiResponse.FAIL("密码输错次数过多,已被锁定2分钟");
            }
        }

        //校验用户是否存在
        User user = getUserByPhone(phone);
        if (user == null) {
            log.error("该用户不存在，请注册后登录");
            return ApiResponse.FAIL(String.format(MessageConstant.LOGIN_ERROR, setLoginCommit(request)));
        }

        //md5校验登录密码
        String MD5Password = MD5Utils.string2MD5(password);
        if (StringUtils.isEmpty(user.getPassword()) || !user.getPassword().equals(MD5Password)) {
            log.error("输入密码错误");
            return ApiResponse.FAIL(String.format(MessageConstant.LOGIN_ERROR, setLoginCommit(request)));
        }

        //生成Token信息
        long expiration = isRememberMe ? isRememberMeExpiresSecond : audience.getExpiresSecond();
        Token token = jwtTokenUtil.createToken(user.getName(), user.getId());
        updateAddress(ip, user);

        // 过滤密码
        user.setPassword("");
        // 添加在线用户到Redis中【设置过期时间】
        loginUserService.addOnlineUser(user, ip, token.getAccessToken(), expiration);

        //将从数据库查询的数据缓存到redis中
        redisUtil.setEx(RedisConstant.USER_TOKEN + CharConstant.SYMBOL_COLON + token.getAccessToken(), JsonUtils.objectToJson(user), expiresSecond, TimeUnit.SECONDS);
        log.info("登录成功, accessToken: {}", token.getAccessToken());

        return ApiResponse.OK(token);
    }

    private static void updateAddress(String ip, User user) {
        String addresses = IpUtils.getAddresses(com.leetroll.common.constant.SysConstant.IP + com.leetroll.common.constant.SysConstant.EQUAL_TO + ip, com.leetroll.common.constant.SysConstant.UTF_8);
        user.setAddress(addresses);
        user.updateById();
    }


    /**
     * 刷新token
     **/
    @ApiOperation(value = "刷新token", notes = "刷新token")
    @PostMapping("/refreshToken")
    public ApiResponse<Token> refreshToken(@RequestParam(name = "refreshToken", required = false) String refreshToken) {
        if (refreshToken == null) {
            return ApiResponse.FAIL("token为空!");
        }
        if (jwtTokenUtil.isExpiration(refreshToken)) {
            return ApiResponse.FAIL(TOKEN_LOSE_EFFECT, "token过期,请重新登录");
        }
        // 生成一个新的Token
        String accessToken = jwtTokenUtil.refreshToken(refreshToken, expiresSecond * 1000);
        return ApiResponse.OK(new Token(accessToken, refreshToken));
    }

    @ApiOperation(value = "用户注册", notes = "用户注册")
    @PostMapping("/register")
    public ApiResponse<String> register(@Validated({Insert.class}) @RequestBody LoginVO loginVO, BindingResult result) {
        ThrowableUtils.checkParamArgument(result);
        if (loginVO.getName().length() < CharConstant.NUM_THREE || loginVO.getName().length() >= CharConstant.NUM_TWENTY) {
            return ApiResponse.FAIL(MessageConstant.NAME_CHECKERROR);
        }
        if (loginVO.getPassword().length() < CharConstant.NUM_THREE || loginVO.getPassword().length() >= CharConstant.NUM_TWENTY) {
            return ApiResponse.FAIL(MessageConstant.PARAM_CHECKERROR);
        }
        User user = getUserByPhone(loginVO.getPhone());
        if (user != null) {
            return ApiResponse.FAIL(MessageConstant.USER_EXIST);
        }
        userService.addUser(loginVO);
        return ApiResponse.OK("注册成功");
    }

    @OperationLogger(modul = EModule.LOGIN_REGIST,type = "LOGINOUT",desc = "用户退出登录")
    @ApiOperation(value = "退出登录", notes = "退出登录", response = String.class)
    @PostMapping(value = "/logout")
    public ApiResponse<String> logout(HttpServletRequest request) {
        //请求头获取accessToken
        String accessToken = request.getHeader("accessToken");
        if (StringUtils.isEmpty(accessToken)) {
            return ApiResponse.FAIL(MessageConstant.OPERATION_FAIL);
        }
        redisUtil.set(RedisConstant.USER_TOKEN + CharConstant.SYMBOL_COLON + accessToken, "");
        return ApiResponse.OK("用户退出成功");
    }

    /**
     * 设置登录限制，返回剩余次数
     * 密码错误五次，将会锁定30分钟
     */
    private Integer setLoginCommit(HttpServletRequest request) {
        String ip = IpUtils.getIpAddr(request);
        //查询redis
        String count = redisUtil.get(RedisConstant.LOGIN_LIMIT + RedisConstant.SEGMENTATION + ip);
        int surplusCount = 5;
        if (StringUtils.isNotEmpty(count)) {
            Integer countTemp = Integer.parseInt(count) + 1;
            surplusCount = surplusCount - countTemp;
            redisUtil.setEx(RedisConstant.LOGIN_LIMIT + RedisConstant.SEGMENTATION + ip, String.valueOf(countTemp), 2, TimeUnit.MINUTES);
        } else {
            surplusCount = surplusCount - 1;
            redisUtil.setEx(RedisConstant.LOGIN_LIMIT + RedisConstant.SEGMENTATION + ip, CharConstant.STR_ONE, 2, TimeUnit.MINUTES);
        }
        return surplusCount;
    }

    private User getUserByPhone(String phone) {
        QueryWrapper<User> queryWrapper = new QueryWrapper<>();
        queryWrapper.eq(SQLConstant.PHONE, phone);
        queryWrapper.last(SysConstant.LIMIT_ONE);
        //查询User表
        return userService.getOne(queryWrapper);
    }

}